Optimize local contrast#79
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 52 minutes and 30 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughAdds a null-check to Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
PhotoLocatorTest/BenchmarkHelper.cs (1)
6-6: Two small polish items.
BenchmarkHelperexposes only static members; mark the classstaticto prevent instantiation.sw.ElapsedMillisecondstruncates to whole milliseconds, which can hide differences for fast operations;sw.Elapsed.TotalMilliseconds(double) orsw.ElapsedTickswould be more useful for benchmarking. TheiterationTimesarray would need to becomedouble[]/long[]accordingly.♻️ Suggested polish
- public class BenchmarkHelper + public static class BenchmarkHelper { public static void Run(Action action, int innerLoops = 1, int outerIterations = 5) { ... - var iterationTimes = new long[outerIterations]; + var iterationTimes = new double[outerIterations]; ... - Console.WriteLine($"Iteration {i + 1}: {sw.ElapsedMilliseconds} ms"); + Console.WriteLine($"Iteration {i + 1}: {sw.Elapsed.TotalMilliseconds:F3} ms"); ... - iterationTimes[i] = sw.ElapsedMilliseconds; + iterationTimes[i] = sw.Elapsed.TotalMilliseconds;Also applies to: 24-24
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PhotoLocatorTest/BenchmarkHelper.cs` at line 6, BenchmarkHelper currently exposes only static members and uses sw.ElapsedMilliseconds which truncates timing precision; mark class BenchmarkHelper as static to prevent instantiation, change the iterationTimes array to double[] (or long[] if you prefer ticks) and replace uses of sw.ElapsedMilliseconds with sw.Elapsed.TotalMilliseconds (or sw.ElapsedTicks) and update all related method signatures/returns that reference iterationTimes or its element type (e.g., any methods that build/return iterationTimes) so types align with the new double[]/long[] choice.PhotoLocator/BitmapOperations/IIRMinMaxOperation.cs (1)
51-51: Nit: typoquaterWidth→quarterWidth.The refactor logic is correct (each of the 4 column states is independent and the tail loop covers the
Stride % 4remainder), but the variable name is misspelled in bothMinFilterandMaxFilter.✏️ Suggested rename
- int quaterWidth = plane.Stride / 4; - Parallel.For(0, quaterWidth, x4 => + int quarterWidth = plane.Stride / 4; + Parallel.For(0, quarterWidth, x4 => ... - Parallel.For(quaterWidth * 4, plane.Stride, x => + Parallel.For(quarterWidth * 4, plane.Stride, x =>Also applies to: 103-103, 174-174, 226-226
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PhotoLocator/BitmapOperations/IIRMinMaxOperation.cs` at line 51, The variable name `quaterWidth` is misspelled in IIRMinMaxOperation.cs; rename it to `quarterWidth` across the file so both MinFilter and MaxFilter use the correct identifier (update the declaration and all usages in methods MinFilter and MaxFilter and the other occurrences noted) to keep naming consistent and avoid confusion or potential shadowing issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@PhotoLocator/BitmapOperations/IIRSmoothOperation.cs`:
- Around line 84-92: In IIRSmoothOperation.cs inside the non-AVX Parallel.For
lambda (the block that uses fixed (float* pixels = plane.Elements) and
Vector4.Load), remove the shadowed local declaration "int vectorizedSegments =
plane.Stride / vectorSize;"—it duplicates the outer variable vectorizedSegments
and is unused; simply delete that line so the lambda uses the captured outer
vectorizedSegments variable.
- Around line 47-109: Replace the AVX/Vector4 platform split in
IIRSmoothOperation.cs with a single implementation using
System.Numerics.Vector<float> so the runtime picks the best SIMD (AVX on x64,
NEON on ARM64) and remove duplicated forward/backward loops; specifically,
eliminate the Avx.IsSupported branch and the Vector4 branch and implement one
Parallel.For over vectorizedSegments that loads/stores using Vector<float> (use
Vector<float>.Count to compute vectorSize), apply filterSize and scale via
Vector.Create and Vector.Multiply/Add, and perform the same forward then
backward propagation using that Vector<float> v; also remove the inner shadowing
declaration int vectorizedSegments = plane.Stride / vectorSize that appears
inside the Vector4 branch (it is unused and shadows the outer variable).
In `@PhotoLocatorTest/BenchmarkHelper.cs`:
- Line 30: The Run method in BenchmarkHelper currently ends by unconditionally
throwing AssertFailedException which makes every benchmark call appear as a
failing test; update Run to surface timings without failing tests by replacing
the final throw of AssertFailedException with a non-failing test outcome (e.g.,
call Assert.Inconclusive($"Min={min} ms, median={median} ms") or return a result
object instead) and document the behavior in a summary comment on the
BenchmarkHelper.Run method so callers know benchmarks intentionally report
timing via inconclusive/returned results rather than true failures; reference
the Run method and the thrown AssertFailedException when making the change.
- Around line 16-25: The code calls GC.EndNoGCRegion() unconditionally even
though GC.TryStartNoGCRegion(...) can return false; modify the loop in
BenchmarkHelper (the block using GC.Collect(), GC.TryStartNoGCRegion, Stopwatch
and iterationTimes) to capture the boolean result (e.g., started =
GC.TryStartNoGCRegion(...)), only call GC.EndNoGCRegion() when started is true,
and ensure EndNoGCRegion() is invoked from a finally block if started to
guarantee cleanup on exceptions; if TryStartNoGCRegion returns false, still run
the timing action but skip EndNoGCRegion().
---
Nitpick comments:
In `@PhotoLocator/BitmapOperations/IIRMinMaxOperation.cs`:
- Line 51: The variable name `quaterWidth` is misspelled in
IIRMinMaxOperation.cs; rename it to `quarterWidth` across the file so both
MinFilter and MaxFilter use the correct identifier (update the declaration and
all usages in methods MinFilter and MaxFilter and the other occurrences noted)
to keep naming consistent and avoid confusion or potential shadowing issues.
In `@PhotoLocatorTest/BenchmarkHelper.cs`:
- Line 6: BenchmarkHelper currently exposes only static members and uses
sw.ElapsedMilliseconds which truncates timing precision; mark class
BenchmarkHelper as static to prevent instantiation, change the iterationTimes
array to double[] (or long[] if you prefer ticks) and replace uses of
sw.ElapsedMilliseconds with sw.Elapsed.TotalMilliseconds (or sw.ElapsedTicks)
and update all related method signatures/returns that reference iterationTimes
or its element type (e.g., any methods that build/return iterationTimes) so
types align with the new double[]/long[] choice.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: db4134a1-5d49-4022-a2c3-9c957f41c32e
📒 Files selected for processing (4)
PhotoLocator/BitmapOperations/FloatBitmap.csPhotoLocator/BitmapOperations/IIRMinMaxOperation.csPhotoLocator/BitmapOperations/IIRSmoothOperation.csPhotoLocatorTest/BenchmarkHelper.cs
There was a problem hiding this comment.
♻️ Duplicate comments (1)
PhotoLocatorTest/BenchmarkHelper.cs (1)
16-25:⚠️ Potential issue | 🟠 Major
GC.EndNoGCRegion()still called unconditionally.
GC.TryStartNoGCRegioncan returnfalse(e.g., on memory-constrained machines or if a no‑GC region is already active), in which case the unconditionalGC.EndNoGCRegion()on line 23 will throwInvalidOperationExceptionand abort the benchmark loop. Capture the return value and only end the region when it actually started. Atry/finallyaround the timed work would also ensure the region is closed ifaction()throws.Proposed fix
for (int i = 0; i < outerIterations; i++) { GC.Collect(); - GC.TryStartNoGCRegion(1024 * 1024 * 100); - var sw = Stopwatch.StartNew(); - for (int j = 0; j < innerLoops; j++) - action(); - sw.Stop(); - Console.WriteLine($"Iteration {i + 1}: {sw.ElapsedMilliseconds} ms"); - GC.EndNoGCRegion(); - iterationTimes[i] = sw.ElapsedMilliseconds; + bool inNoGc = GC.TryStartNoGCRegion(1024 * 1024 * 100); + var sw = Stopwatch.StartNew(); + try + { + for (int j = 0; j < innerLoops; j++) + action(); + } + finally + { + sw.Stop(); + if (inNoGc && GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) + GC.EndNoGCRegion(); + } + Console.WriteLine($"Iteration {i + 1}: {sw.ElapsedMilliseconds} ms"); + iterationTimes[i] = sw.ElapsedMilliseconds; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PhotoLocatorTest/BenchmarkHelper.cs` around lines 16 - 25, GC.EndNoGCRegion() is being called unconditionally which can throw if GC.TryStartNoGCRegion(...) returned false; modify the loop around the benchmark so you capture the boolean result of GC.TryStartNoGCRegion(1024 * 1024 * 100) into a variable (e.g., started), wrap the timed work (Stopwatch sw, the for loop calling action() and recording sw.ElapsedMilliseconds into iterationTimes) in a try/finally, and only call GC.EndNoGCRegion() in the finally when started is true so you always end the region if you started it but never call EndNoGCRegion when TryStartNoGCRegion failed.
🧹 Nitpick comments (1)
PhotoLocator/BitmapOperations/IIRSmoothOperation.cs (1)
45-46: Redundantunsafeblock.Line 19 already opens an
unsafeblock, so the nestedunsafeon line 45 is superfluous and only adds indentation. Consider removing it for consistency with the horizontal pass above.♻️ Proposed simplification
- // Vertical smooth - vectorized over columns when possible - unsafe - { - int vectorSize = Vector<float>.Count; - int vectorizedSegments = plane.Stride / vectorSize; - var filterSizeV = Vector.Create(filterSize); - var scaleV = Vector.Create(scale); - Parallel.For(0, vectorizedSegments, vi => - { - ... - }); - - // Remaining columns - columns not divisible by vectorSize - Parallel.For(vectorizedSegments * vectorSize, plane.Stride, x => - { - ... - }); - } + // Vertical smooth - vectorized over columns when possible + int vectorSize = Vector<float>.Count; + int vectorizedSegments = plane.Stride / vectorSize; + var filterSizeV = Vector.Create(filterSize); + var scaleV = Vector.Create(scale); + Parallel.For(0, vectorizedSegments, vi => + { + ... + }); + + // Remaining columns - columns not divisible by vectorSize + Parallel.For(vectorizedSegments * vectorSize, plane.Stride, x => + { + ... + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PhotoLocator/BitmapOperations/IIRSmoothOperation.cs` around lines 45 - 46, The nested unsafe block inside the IIRSmoothOperation vertical pass is redundant because an outer unsafe is already in scope; remove the inner "unsafe { ... }" wrapper and de-indent its contents so the code inside continues to execute under the existing outer unsafe block (locate the nested unsafe in the IIRSmoothOperation class and collapse it, mirroring how the horizontal pass is formatted).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@PhotoLocatorTest/BenchmarkHelper.cs`:
- Around line 16-25: GC.EndNoGCRegion() is being called unconditionally which
can throw if GC.TryStartNoGCRegion(...) returned false; modify the loop around
the benchmark so you capture the boolean result of GC.TryStartNoGCRegion(1024 *
1024 * 100) into a variable (e.g., started), wrap the timed work (Stopwatch sw,
the for loop calling action() and recording sw.ElapsedMilliseconds into
iterationTimes) in a try/finally, and only call GC.EndNoGCRegion() in the
finally when started is true so you always end the region if you started it but
never call EndNoGCRegion when TryStartNoGCRegion failed.
---
Nitpick comments:
In `@PhotoLocator/BitmapOperations/IIRSmoothOperation.cs`:
- Around line 45-46: The nested unsafe block inside the IIRSmoothOperation
vertical pass is redundant because an outer unsafe is already in scope; remove
the inner "unsafe { ... }" wrapper and de-indent its contents so the code inside
continues to execute under the existing outer unsafe block (locate the nested
unsafe in the IIRSmoothOperation class and collapse it, mirroring how the
horizontal pass is formatted).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4fa8911a-b8d4-4d70-8ac1-7a9d0abcd0d0
📒 Files selected for processing (3)
PhotoLocator/BitmapOperations/IIRMinMaxOperation.csPhotoLocator/BitmapOperations/IIRSmoothOperation.csPhotoLocatorTest/BenchmarkHelper.cs
🚧 Files skipped from review as they are similar to previous changes (1)
- PhotoLocator/BitmapOperations/IIRMinMaxOperation.cs
Summary by CodeRabbit
Bug Fixes
Performance Improvements
Tests